#ifndef EMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define EMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66

#if defined(_MSC_VER) ||                                            \
    (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \
     (__GNUC__ >= 4)) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include <cmath>
#include <cstddef>
#include <limits>
#include <memory>
#include <sstream>
#include <string>
#include <type_traits>

#include "yaml-cpp/binary.h"
#include "yaml-cpp/dll.h"
#include "yaml-cpp/emitterdef.h"
#include "yaml-cpp/emittermanip.h"
#include "yaml-cpp/null.h"
#include "yaml-cpp/ostream_wrapper.h"

namespace YAML {
class Binary;
struct _Null;
} // namespace YAML

namespace YAML {
class EmitterState;

class YAML_CPP_API Emitter {
public:
    Emitter();
    explicit Emitter(std::ostream& stream);
    Emitter(const Emitter&) = delete;
    Emitter& operator=(const Emitter&) = delete;
    ~Emitter();

    // output
    const char* c_str() const;
    std::size_t size() const;

    // state checking
    bool good() const;
    const std::string GetLastError() const;

    // global setters
    bool SetOutputCharset(EMITTER_MANIP value);
    bool SetStringFormat(EMITTER_MANIP value);
    bool SetBoolFormat(EMITTER_MANIP value);
    bool SetNullFormat(EMITTER_MANIP value);
    bool SetIntBase(EMITTER_MANIP value);
    bool SetSeqFormat(EMITTER_MANIP value);
    bool SetMapFormat(EMITTER_MANIP value);
    bool SetIndent(std::size_t n);
    bool SetPreCommentIndent(std::size_t n);
    bool SetPostCommentIndent(std::size_t n);
    bool SetFloatPrecision(std::size_t n);
    bool SetDoublePrecision(std::size_t n);
    void RestoreGlobalModifiedSettings();

    // local setters
    Emitter& SetLocalValue(EMITTER_MANIP value);
    Emitter& SetLocalIndent(const _Indent& indent);
    Emitter& SetLocalPrecision(const _Precision& precision);

    // overloads of write
    Emitter& Write(const std::string& str);
    Emitter& Write(bool b);
    Emitter& Write(char ch);
    Emitter& Write(const _Alias& alias);
    Emitter& Write(const _Anchor& anchor);
    Emitter& Write(const _Tag& tag);
    Emitter& Write(const _Comment& comment);
    Emitter& Write(const _Null& n);
    Emitter& Write(const Binary& binary);

    template <typename T>
    Emitter& WriteIntegralType(T value);

    template <typename T>
    Emitter& WriteStreamable(T value);

private:
    template <typename T>
    void SetStreamablePrecision(std::stringstream&) {}
    std::size_t GetFloatPrecision() const;
    std::size_t GetDoublePrecision() const;

    void PrepareIntegralStream(std::stringstream& stream) const;
    void StartedScalar();

private:
    void EmitBeginDoc();
    void EmitEndDoc();
    void EmitBeginSeq();
    void EmitEndSeq();
    void EmitBeginMap();
    void EmitEndMap();
    void EmitNewline();
    void EmitKindTag();
    void EmitTag(bool verbatim, const _Tag& tag);

    void PrepareNode(EmitterNodeType::value child);
    void PrepareTopNode(EmitterNodeType::value child);
    void FlowSeqPrepareNode(EmitterNodeType::value child);
    void BlockSeqPrepareNode(EmitterNodeType::value child);

    void FlowMapPrepareNode(EmitterNodeType::value child);

    void FlowMapPrepareLongKey(EmitterNodeType::value child);
    void FlowMapPrepareLongKeyValue(EmitterNodeType::value child);
    void FlowMapPrepareSimpleKey(EmitterNodeType::value child);
    void FlowMapPrepareSimpleKeyValue(EmitterNodeType::value child);

    void BlockMapPrepareNode(EmitterNodeType::value child);

    void BlockMapPrepareLongKey(EmitterNodeType::value child);
    void BlockMapPrepareLongKeyValue(EmitterNodeType::value child);
    void BlockMapPrepareSimpleKey(EmitterNodeType::value child);
    void BlockMapPrepareSimpleKeyValue(EmitterNodeType::value child);

    void SpaceOrIndentTo(bool requireSpace, std::size_t indent);

    const char* ComputeFullBoolName(bool b) const;
    const char* ComputeNullName() const;
    bool CanEmitNewline() const;

private:
    std::unique_ptr<EmitterState> m_pState;
    ostream_wrapper m_stream;
};

template <typename T>
inline Emitter& Emitter::WriteIntegralType(T value) {
    if (!good())
        return *this;

    PrepareNode(EmitterNodeType::Scalar);

    std::stringstream stream;
    PrepareIntegralStream(stream);
    stream << value;
    m_stream << stream.str();

    StartedScalar();

    return *this;
}

template <typename T>
inline Emitter& Emitter::WriteStreamable(T value) {
    if (!good())
        return *this;

    PrepareNode(EmitterNodeType::Scalar);

    std::stringstream stream;
    SetStreamablePrecision<T>(stream);

    bool special = false;
    if (std::is_floating_point<T>::value) {
        if ((std::numeric_limits<T>::has_quiet_NaN ||
             std::numeric_limits<T>::has_signaling_NaN) &&
            std::isnan(value)) {
            special = true;
            stream << ".nan";
        }
        else if (std::numeric_limits<T>::has_infinity && std::isinf(value)) {
            special = true;
            if (std::signbit(value)) {
                stream << "-.inf";
            }
            else {
                stream << ".inf";
            }
        }
    }

    if (!special) {
        stream << value;
    }
    m_stream << stream.str();

    StartedScalar();

    return *this;
}

template <>
inline void Emitter::SetStreamablePrecision<float>(std::stringstream& stream) {
    stream.precision(static_cast<std::streamsize>(GetFloatPrecision()));
}

template <>
inline void Emitter::SetStreamablePrecision<double>(std::stringstream& stream) {
    stream.precision(static_cast<std::streamsize>(GetDoublePrecision()));
}

// overloads of insertion
inline Emitter& operator<<(Emitter& emitter, const std::string& v) {
    return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, bool v) {
    return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, char v) {
    return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned char v) {
    return emitter.Write(static_cast<char>(v));
}
inline Emitter& operator<<(Emitter& emitter, const _Alias& v) {
    return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const _Anchor& v) {
    return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const _Tag& v) {
    return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const _Comment& v) {
    return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const _Null& v) {
    return emitter.Write(v);
}
inline Emitter& operator<<(Emitter& emitter, const Binary& b) {
    return emitter.Write(b);
}

inline Emitter& operator<<(Emitter& emitter, const char* v) {
    return emitter.Write(std::string(v));
}

inline Emitter& operator<<(Emitter& emitter, int v) {
    return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned int v) {
    return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, short v) {
    return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned short v) {
    return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, long v) {
    return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned long v) {
    return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, long long v) {
    return emitter.WriteIntegralType(v);
}
inline Emitter& operator<<(Emitter& emitter, unsigned long long v) {
    return emitter.WriteIntegralType(v);
}

inline Emitter& operator<<(Emitter& emitter, float v) {
    return emitter.WriteStreamable(v);
}
inline Emitter& operator<<(Emitter& emitter, double v) {
    return emitter.WriteStreamable(v);
}

inline Emitter& operator<<(Emitter& emitter, EMITTER_MANIP value) {
    return emitter.SetLocalValue(value);
}

inline Emitter& operator<<(Emitter& emitter, _Indent indent) {
    return emitter.SetLocalIndent(indent);
}

inline Emitter& operator<<(Emitter& emitter, _Precision precision) {
    return emitter.SetLocalPrecision(precision);
}
} // namespace YAML

#endif // EMITTER_H_62B23520_7C8E_11DE_8A39_0800200C9A66
